#include <string>
#include <thread>
#include <iostream>
#include <mutex>
/**
 * 单例模式类定义了 `GetInstance` 方法，作为构造函数的替代方案，
 * 让客户端可以重复访问该类的同一个实例。
 */
class Singleton{
    /**
     * 单例的构造函数/析构函数应该始终为私有，以防止使用 `new`/`delete`
     * 运算符进行直接的构造/销毁调用。
     */
private:
    static Singleton * pinstance_;
    static std::mutex mutex_;
protected:
    Singleton(const std::string value): value_(value) {}
    ~Singleton() {}
    std::string value_;
public:
    Singleton(const Singleton&) = delete;
    Singleton &operator=(const Singleton&) = delete;

    /**
     * 这是控制对单例实例访问的静态方法。
     * 第一次运行时，它会创建一个单例对象并将其放入静态字段中。
     * 在后续运行中，它返回存储在静态字段中的现有对象。
     */
    static Singleton *GetInstance(const std::string& value);

    void SomeBusinessLogic(){}

    std::string value() const{
        return value_;
    }
};

// 初始化静态成员变量
Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;

/**
 * 第一次调用 GetInstance 时，我们会锁定存储位置，
 * 然后再次检查变量是否为 null，接着设置值。
 * 这种方法称为双重检查锁定的变体，但这里使用了简单的互斥锁保护。
 */
Singleton *Singleton::GetInstance(const std::string& value){
    std::lock_guard<std::mutex> lock(mutex_);
    if (pinstance_ == nullptr){
        pinstance_ = new Singleton(value);
    }
    return pinstance_;
}

/**
 * 线程函数 Foo：模拟延迟初始化
 */
void ThreadFoo(){
    // 以下代码模拟缓慢的初始化过程
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance("FOO");  // 尝试获取值为 "FOO" 的单例
    std::cout << singleton->value() << "\n";               // 输出单例的值
}

/**
 * 线程函数 Bar：模拟延迟初始化
 */
void ThreadBar(){
    // 以下代码模拟缓慢的初始化过程
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance("BAR");  // 尝试获取值为 "BAR" 的单例
    std::cout << singleton->value() << "\n";               // 输出单例的值
}

int main()
{   
    std::cout << "如果看到相同的值，说明单例被重用了（正确！）\n" 
              << "如果看到不同的值，说明创建了2个单例（错误！）\n\n" 
              << "运行结果:\n";   
    
    // 创建两个线程同时访问单例
    std::thread t1(ThreadFoo);
    std::thread t2(ThreadBar);
    
    // 等待线程结束
    t1.join();
    t2.join();
    
    return 0;
}